using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using System.Xml;
using GGEditor.API;
using GGEditor.API.Plugins;

namespace GGEditor.Core.Plugins
{
	public class XmlPluginLoader
	{
		private XmlPluginLoader()
		{
			passes.Add(new HeaderLoaderPass());
			passes.Add(new AssemblyLoaderPass());
			passes.Add(new XmlFactoryLoaderPass());
			passes.Add(new ExtensionNodeTreePass());
		}

		private static XmlPluginLoader instance =
			new XmlPluginLoader();

		private IList<IXmlPluginLoaderPass> passes =
			new List<IXmlPluginLoaderPass>();

		public static XmlPluginLoader Instance
		{
			get { return instance; }
		}

		public IPlugin Load(string filePath)
		{
			if (!File.Exists(filePath))
				throw new FileNotFoundException(
					string.Format("Plugin file \"{0}\" not found.", filePath));

			PluginBuilder plugin = new PluginBuilder()
			{
				DirPath = Path.GetDirectoryName(filePath)
			};

			XmlDocument doc = new XmlDocument();
			doc.Load(filePath);

			if (doc.DocumentElement == null || doc.DocumentElement.Name != "Plugin")
				throw new InvalidDataException(
					string.Format("Plugin file \"{0}\" doesn't have a " +
					"\"Plugin\" element as its root.", filePath));

			foreach (IXmlPluginLoaderPass pass in passes)
			{
				pass.Run(doc, plugin);
			}

			return plugin;
		}

		interface IXmlPluginLoaderPass
		{
			void Run(XmlDocument doc, PluginBuilder plugin);
		}

		class HeaderLoaderPass : IXmlPluginLoaderPass
		{
			#region Implementation of IXmlPluginLoaderPass

			public void Run(XmlDocument doc, PluginBuilder plugin)
			{
				try
				{
					plugin.Name = doc.DocumentElement.Attributes["name"].InnerText;
				}
				catch
				{
					throw new InvalidDataException();
					//throw new InvalidDataException(
					//    string.Format("Plugin file \"{0}\" doesn't have a " +
					//                  "\"name\" attribute.", filePath));
				}

				try
				{
					plugin.Version = doc.DocumentElement.Attributes["version"].InnerText;
				}
				catch
				{
					throw new InvalidDataException();
					//throw new InvalidDataException(
					//    string.Format("Plugin file \"{0}\" doesn't have a " +
					//                  "\"version\" attribute.", filePath));
				}

				try
				{
					plugin.Description = doc.DocumentElement.Attributes["description"].InnerText;
				}
				catch
				{
					plugin.Description = "";
				}

				try
				{
					plugin.Author = doc.DocumentElement.Attributes["author"].InnerText;
				}
				catch
				{
					plugin.Author = "";
				}

				try
				{
					plugin.Url = doc.DocumentElement.Attributes["url"].InnerText;
				}
				catch
				{
					plugin.Url = "";
				}

				try
				{
					plugin.Copyright = doc.DocumentElement.Attributes["copyright"].InnerText;
				}
				catch
				{
					plugin.Copyright = "";
				}
			}

			#endregion
		}

		class AssemblyLoaderPass : IXmlPluginLoaderPass
		{
			#region Implementation of IXmlPluginLoaderPass

			public void Run(XmlDocument doc, PluginBuilder plugin)
			{
				foreach (object o in doc.DocumentElement.ChildNodes)
				{
					XmlElement e = o as XmlElement;
					if (e == null)
						continue;
					if (e.Name == "Assembly")
					{
						string fileName = e.Attributes["name"].InnerText;
						Assembly assembly = PluginAssemblyManager.Instance.LoadAssembly(fileName);
						plugin.AddAssembly(assembly);
						XmlNodeObjectFactory.Instance.RegisterFromAssembly(assembly);
					}
				}
			}

			#endregion
		}

		class XmlFactoryLoaderPass : IXmlPluginLoaderPass
		{
			#region Implementation of IXmlPluginLoaderPass

			public void Run(XmlDocument doc, PluginBuilder plugin)
			{
				foreach (object o in doc.DocumentElement.ChildNodes)
				{
					XmlElement e = o as XmlElement;
					if (e == null)
						continue;
					if (e.Name == "NodeType")
					{
						string name = e.Attributes["name"].InnerText;
						string typeName = e.Attributes["type"].InnerText;
						
					}
				}
			}

			#endregion
		}

		class ExtensionNodeTreePass : IXmlPluginLoaderPass
		{
			#region Implementation of IXmlPluginLoaderPass

			public void Run(XmlDocument doc, PluginBuilder plugin)
			{
				foreach (object o in doc.DocumentElement.ChildNodes)
				{
					XmlElement e = o as XmlElement;
					if (e == null || e.Name != "Extension")
						continue;
					IPluginExtension extension = GetExtension(e, plugin);
					if (extension != null)
						plugin.AddExtension(extension);
				}
			}

			private IPluginExtension GetExtension(XmlElement e, PluginBuilder plugin)
			{
				string extensionPath = e.Attributes["path"].InnerText;

				IPluginNode extensionNode = PluginManager.Instance.GetNode(extensionPath);

				if (extensionNode == null)
					throw new PluginLoadDependencyException();

				PluginExtensionBuilder extension = new PluginExtensionBuilder(extensionPath);

				foreach (object o in e.ChildNodes)
				{
					XmlElement eChild = o as XmlElement;
					if (eChild == null)
						continue;
					IPluginNode node = GetNode(eChild, extensionNode, plugin);
					extension.AddNode(node);
				}

				return extension;
			}

			private IPluginNode GetNode(XmlElement e, IPluginNode parentNode, PluginBuilder plugin)
			{
				IDictionary<string, string> attributes
					= new Dictionary<string, string>();

				foreach (XmlAttribute attribute in e.Attributes)
				{
					attributes[attribute.LocalName] = attribute.InnerText;
				}

				PluginNodeBuilder node = new PluginNodeBuilder()
				{
					Class = e.Name,
					Name = attributes["name"],
					Parent = parentNode,
					Plugin = plugin
				};

				IXmlNodeObjectFactory factory = XmlNodeObjectFactory.Instance.GetFactory(node.Class);

				node.Object = factory.Build(node, attributes);

				foreach (object o in e.ChildNodes)
				{
					XmlElement eChild = o as XmlElement;
					if (eChild == null)
						continue;
					IPluginNode childNode = GetNode(eChild, node, plugin);
					node.AddChild(childNode);
				}

				return node;
			}

			#endregion
		}
	}
}